package controllers

import (
    "gouniapp/configs"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"time"
    "log"
    "crypto/md5"
    "encoding/hex"
    "sync"
	"github.com/gin-gonic/gin"
)

// 全局锁Map
var downloadLocks sync.Map

// 请求参数结构体
type ParseRequest struct {
	URL string `json:"url" binding:"required"`
}
// 获取文件md5值
func getVideoHash(videoURL string) string {
    hasher := md5.New()
    hasher.Write([]byte(videoURL))
    return hex.EncodeToString(hasher.Sum(nil))
}

// 视频解析接口
func ParseVideoUrl(c *gin.Context) {
	// 1. 绑定请求参数
	var req ParseRequest
	if err := c.ShouldBindJSON(&req); err != nil {
	    JsonResponse(c, http.StatusBadRequest, "请求参数无效", nil)
		return
	}

	// 2. 验证URL格式
	if !isValidVideoURL(req.URL) {
        JsonResponse(c, http.StatusBadRequest, "视频url无效", nil)
		return
	}
	// 3. 调用第三方解析API
	videoURL, err := getRealVideoURL(req.URL)
	if err != nil {
	    JsonResponse(c, http.StatusInternalServerError, "接口失败: " + err.Error(), nil)
		return
	}

	// 4. 下载视频到本地
	localPath, err := downloadAndSaveVideo(videoURL, req.URL)
	if err != nil {
	    JsonResponse(c, http.StatusInternalServerError, "下载视频到本地失败: " + err.Error(), nil)
		return
	}

	// 5. 返回本地访问地址
	fullURL, _ := url.JoinPath(configs.MyURL, localPath)
	JsonResponse(c, http.StatusOK, "获取成功", gin.H{
		"url":  fullURL,
	})
}

// 验证视频链接有效性（增强版）
func isValidVideoURL(rawURL string) bool {
	u, err := url.Parse(rawURL)
	if err != nil {
		return false
	}

	// 域名白名单
	allowedDomains := []string{
		"douyin.com", "iesdouyin.com",
		"kuaishou.com", "chenzhongtech.com",
		"bilibili.com", "b23.tv",
	}

	// 检查域名
	domainValid := false
	for _, domain := range allowedDomains {
		if strings.Contains(u.Hostname(), domain) {
			domainValid = true
			break
		}
	}
	if !domainValid {
		return false
	}

	// 路径规则验证
	rules := []struct {
		hostPattern *regexp.Regexp
		pathPattern *regexp.Regexp
	}{
		// 抖音规则
        {
            hostPattern: regexp.MustCompile(`douyin\.com|iesdouyin\.com`),
            pathPattern: regexp.MustCompile(`^(/\w+/video/\d+|/[A-Za-z0-9_-]+/?$)`),
        },
		// 快手规则
		{
			hostPattern: regexp.MustCompile(`kuaishou\.com|chenzhongtech\.com`),
			pathPattern: regexp.MustCompile(`^/(f|short-video)/\w+`),
		},
		// B站规则
		{
			hostPattern: regexp.MustCompile(`bilibili\.com|b23\.tv`),
			pathPattern: regexp.MustCompile(`/video/[a-zA-Z0-9]+`),
		},
	}

	for _, rule := range rules {
		if rule.hostPattern.MatchString(u.Hostname()) && 
			rule.pathPattern.MatchString(u.Path) {
			return true
		}
	}
	return false
}

// 判断是否为抖音短链接
func isDouyinShortLink(rawURL string) bool {
    u, _ := url.Parse(rawURL)
    return strings.Contains(u.Hostname(), "douyin.com") && 
        len(u.Path) > 2 && // 短链接路径类似 "/Ot-cLXKT-J4/"
        !strings.Contains(u.Path, "/video/")
}

// 获取真实长链接
func getLongURL(shortURL string) (string, error) {
    client := &http.Client{
        CheckRedirect: func(req *http.Request, via []*http.Request) error {
            return http.ErrUseLastResponse // 禁止自动跳转
        },
        Timeout: 5 * time.Second,
    }

    resp, err := client.Get(shortURL)
    if err != nil {
        return "", err
    }
    defer resp.Body.Close()

    if resp.StatusCode >= 300 && resp.StatusCode < 400 {
        return resp.Header.Get("Location"), nil
    }
    return "", fmt.Errorf("failed to get redirect URL")
}

// 第三方API响应结构体
type ThirdPartyResponse struct {
	Code    int     `json:"code"`
	Name    string  `json:"name"`
	Title   string  `json:"title"`
	Video   string  `json:"video"`
	Yvideo  string  `json:"yvideo"`
}

// 获取真实视频地址
func getRealVideoURL(rawURL string) (string, error) {
    // 如果是抖音短链接，先获取真实地址
    if isDouyinShortLink(rawURL) {
        longURL, err := getLongURL(rawURL)
        if err != nil {
            return "", err
        }
        rawURL = longURL
    }
	// 编码原始URL10004673
	encodedURL := url.QueryEscape(rawURL)
	apiURL := fmt.Sprintf("http://101.35.2.25/api/fun/douyin.php?id=10004673&key=066a5c603740b8cf9d497f064d3ce090&url=%s", encodedURL)
	resp, err := http.Get(apiURL)
	if err != nil {
		return "", fmt.Errorf("third-party API request failed: %v", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return "", fmt.Errorf("third-party API returned %d status", resp.StatusCode)
	}
	var result ThirdPartyResponse
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		return "", fmt.Errorf("failed to decode response: %v", err)
	}
	if result.Code != 200 {
		return "", fmt.Errorf("解析失败")
	}
	// 优先使用play_url，不存在时使用url
	videoURL := result.Video
	if videoURL == "" {
		videoURL = result.Yvideo
	}
	if videoURL == "" {
		return "", fmt.Errorf("解析失败")
	}
	return videoURL, nil
}

// 改进后的下载函数
func downloadAndSaveVideo(videoURL, oldurl string) (string, error) {
    // 生成哈希值
    hash := getVideoHash(oldurl)
    // 创建存储目录
    saveDir := filepath.Join("uploads", "videos", "blog")
    if err := os.MkdirAll(saveDir, 0755); err != nil {
        return "", fmt.Errorf("目录创建失败: %v", err)
    }
    // 生成文件路径
    fileName := hash + ".mp4"
    filePath := filepath.Join(saveDir, fileName)

    // 第一次快速检查文件是否存在
    if _, err := os.Stat(filePath); err == nil {
        return filePath, nil
    }

    // 获取哈希对应的锁
    lock, _ := downloadLocks.LoadOrStore(hash, &sync.Mutex{})
    mu := lock.(*sync.Mutex)
    mu.Lock()
    defer mu.Unlock()

    // 再次检查文件是否存在（防止重复下载）
    if _, err := os.Stat(filePath); err == nil {
        return filePath, nil
    }

    // 带重试机制的下载流程
    var lastErr error
    for i := 0; i < 3; i++ {
        if i > 0 {
            time.Sleep(time.Duration(i) * time.Second)
        }

        client := &http.Client{
            Timeout: 5 * time.Minute,
            Transport: &http.Transport{
                DisableKeepAlives:      true,
                IdleConnTimeout:       30 * time.Second,
                TLSHandshakeTimeout:   10 * time.Second,
                ResponseHeaderTimeout: 10 * time.Second,
            },
        }

        // 创建临时文件（使用进程ID避免冲突）
        tempFile := fmt.Sprintf("%s.%d.tmp", filePath, os.Getpid())
        out, err := os.Create(tempFile)
        if err != nil {
            lastErr = fmt.Errorf("failed to create temp file: %v", err)
            continue
        }

        // 执行下载
        if err := func() error {
            defer out.Close()
            resp, err := client.Get(videoURL)
            if err != nil {
                return fmt.Errorf("download failed: %v", err)
            }
            defer resp.Body.Close()

            if resp.StatusCode != http.StatusOK {
                return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
            }

            // 带进度监控的下载
            progressWriter := &progressWriter{
                total:    resp.ContentLength,
                file:     out,
                interval: 10 * time.Second,
                done:     make(chan struct{}),
            }
            defer close(progressWriter.done)

            go progressWriter.start()

            if _, err := io.Copy(out, resp.Body); err != nil {
                return fmt.Errorf("copy failed: %v", err)
            }

            return nil
        }(); err != nil {
            os.Remove(tempFile)
            lastErr = err
            continue
        }

        // 原子性重命名
        if err := os.Rename(tempFile, filePath); err != nil {
            os.Remove(tempFile)
            lastErr = fmt.Errorf("rename failed: %v", err)
            continue
        }

        log.Printf("[Download] Successfully saved to %s", filePath)
        return filePath, nil
    }

    return "", fmt.Errorf("after 3 attempts: %v", lastErr)
}

// 进度监控结构体
type progressWriter struct {
    total    int64
    file     *os.File
    interval time.Duration
    done     chan struct{}
}

func (pw *progressWriter) start() {
    ticker := time.NewTicker(pw.interval)
    defer ticker.Stop()
    
    for {
        select {
        case <-ticker.C:
            fi, err := pw.file.Stat()
            if err != nil {
                continue
            }
            progress := float64(fi.Size()) / float64(pw.total) * 100
            if pw.total > 0 {
                log.Printf("[Progress] %.2f%% (%.2f/%.2f MB)",
                    progress,
                    float64(fi.Size())/1024/1024,
                    float64(pw.total)/1024/1024)
            } else {
                log.Printf("[Progress] %.2f MB downloaded",
                    float64(fi.Size())/1024/1024)
            }
        case <-pw.done:
            return
        }
    }
}